//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO.Packaging;
using System.IO;
using System.Globalization;
using System.Data.Common;

namespace Microsoft.Research.DataLayer
{
    public class OPCConnection : NoCodeBehind
    {
        private static Package myPackage;
        private static string packageFilePath;

        /// <summary>
        /// Initialize static shared data
        /// </summary>
        static OPCConnection()
        {
            
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="fileName"></param>
        private OPCConnection(string fileName)
        {
            CreatePropHandlers();
            CreateBlobHandlers();
            packageFilePath = fileName;
        }

        /// <summary>
        /// Creates a new XML Connection
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static OPCConnection Create(string path)
        {
            return new OPCConnection(path);
        }

        /// <summary>
        /// Creates a XML Connection from the stored parameters
        /// </summary>
        /// <param name="connInfo"></param>
        /// <returns></returns>
        public static OPCConnection Create(OPCConnectionParameters connInfo)
        {
            string path;
            connInfo.Load(out path);
            return new OPCConnection(path);
        }

        /// <summary>
        /// Displays summary information about the connection
        /// </summary>
        public override string DisplayInfo
        {
            get
            {
                return packageFilePath;
            }
        }

        /// <summary>
        /// Returns true if the OPC file still exists
        /// </summary>
        public override bool Alive
        {
            get
            {
                return File.Exists(packageFilePath);
            }
        }

        /// <summary>
        /// Open the Package
        /// </summary>
        public override void Open()
        {
            //if the file exists, don't initialize it, 
            //otherwise write the root elmement.
            try
            {
                myPackage = Package.Open(packageFilePath);
            }
            catch (Exception e)
            {
                throw new ConnectionFailure("Cannot create a Package", e);
            }
        }

        /// <summary>
        /// Closes the Package
        /// </summary>
        public override void Close()
        {
            myPackage.Close();
        }

        public void Save()
        {
            // Save the document. Not sure if this is needed
            // but there is no need to save the package explicitly
            // as each write it commited to the file.
            myPackage.Close();
        }

        /// <summary>
        /// Clones a connection
        /// </summary>
        /// <returns></returns>
        public override Connection Clone()
        {
            //It is a bad idea to clone a connection to a file.
            throw new NotImplementedException("Not implemented.");
        }

        /// <summary>
        /// Copy required parameters
        /// </summary>
        /// <param name="src"></param>
        protected override void CopyFrom(Connection src)
        {
            throw new NotImplementedException("CopyFrom");
        }

        protected override void InsertRecord(Connection.Storage obj1, Connection.Storage obj2,
            object objId, ref List<Parameter> paramList)
        {
            string objKind = ObjectKind(obj1, obj2);

            if (this.blobHandlers.ContainsKey(objKind))
            {
                //SaveContents save = this.blobSaveHandlers[objKind];
                //save(obj1, obj2, objId, ref paramList);
                // Do not insert special types as they will be handles in updates.
                Parameter.GetParamByName("ID", paramList).Value = objId;
                return;
            }

            Parameter idParam = Parameter.GetParamByName("ID", paramList);
            if (idParam != null)
                idParam.Value = objId;

            string elemUri = String.Concat("/Trident/" + objKind + "/", objId.ToString() + ".xml");
            Uri partUri = new Uri(elemUri, UriKind.Relative);

            XmlDocument doc = new XmlDocument();
            XmlNode objectNode = doc.CreateNode(XmlNodeType.Element, "ObjectDetails", "");

            foreach (Parameter param in paramList)
            {
                if (param.Value != null &&
                    !(param.Value is DateTime && param.Value.Equals(DateTime.MinValue)))
                {
                    XmlNode paramNode = doc.CreateNode(XmlNodeType.Element, param.Name, "");
                    paramNode.InnerText = param.Value.ToString();

                    XmlAttribute paramType = doc.CreateAttribute("Type");
                    paramType.Value = param.Type.ToString();
                    paramNode.Attributes.Append(paramType);

                    objectNode.AppendChild(paramNode);
                }
            }

            doc.AppendChild(objectNode);
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
            byte[] data = encoding.GetBytes(doc.OuterXml);

            try
            {
                PackagePart part = myPackage.CreatePart(partUri, GetUri(objKind));
                part.GetStream().Write(data, 0, data.Length);
            }
            catch (Exception e)
            {
                throw new DataAccessException("Error while getting package element", e);
            }

            myPackage.Flush();

            if (IsPropHandlerPresent(objKind, PropAction.Insert))
            {
                PropHandlerBase.InsertHandler insert = this.propHandlers[objKind].insertHandler;
                insert(obj1, obj2, objId, ref paramList, objKind, ref myPackage, "trident/tempobject"); 
            }
        }

        protected override void RetrieveRecordByName(Connection.Storage obj, ref List<Parameter> paramList)
        {
            Parameter idParam = Parameter.GetParamByName("ID", paramList);
            Parameter nameParam = Parameter.GetParamByName("Name", paramList);

            string objKind = ObjectKind(obj);
            List<XmlNode> elements = GetPartFromName(nameParam.Value.ToString());
            OPCDataReader reader = new OPCDataReader(elements);

            if (!reader.HasRows)
            {
                throw new RecordNotFoundException("Data not found");
            }

            reader.Read();

            foreach (Parameter p in paramList)
            {
                if (p is IPipelineParameter)
                {
                    IPipelineParameter param = p as IPipelineParameter;
                    param.Value = param.Creator(reader, this);
                }
            }
        }

        protected override void RetrieveRecordByID(Connection.Storage obj, ref List<Parameter> paramList)
        {
            Parameter idParam = Parameter.GetParamByName("ID", paramList);
            string objKind = ObjectKind(obj);

            string elemUri = String.Concat("/Trident/" + objKind + "/", idParam.Value.ToString() + ".xml");

            List<XmlNode> elements = GetPartFromId(elemUri);
            if (null == elements)
            {
                throw new RecordNotFoundException("Record does not exist");
            }
            OPCDataReader reader = new OPCDataReader(elements);

            if (!reader.HasRows)
            {
                throw new RecordNotFoundException("Data not found");
            }

            reader.Read();

            foreach (Parameter p in paramList)
            {
                if (p is IPipelineParameter)
                {
                    IPipelineParameter param = p as IPipelineParameter;
                    param.Value = param.Creator(reader, this);
                }
            }
        }

        protected override void RetrieveAllRecords(Connection.Storage obj, Connection.ObjectCreator creator,
            out List<IObject> results)
        {
            string objKind = ObjectKind(obj);
            results = new List<IObject>();

            List<XmlNode> elements = GetAllPartsOfType(objKind);
            

            if (elements.Count == 0)
            {
                throw new RecordNotFoundException("Data not found");
            }

            OPCDataReader reader = new OPCDataReader(elements);

            if (!reader.HasRows)
            {
                throw new RecordNotFoundException("Data not found");
            }

            while (reader.Read())
            {
                results.Add(creator(reader, this));
            }
        }

        /// <summary>
        /// All relations in OPC are made between a source and a target object. However
        /// the relations will can be retrieved only when the query is made from the 
        /// source and not from the target. Thus each relation is created using an
        /// id which is a combination of guid's of the two objects in which the relation
        /// is being made. This method like the GetObjectKind method will always
        /// return the id's of the two objects in the correct order.
        /// </summary>
        /// <param name="obj1"></param>
        /// <param name="obj2"></param>
        /// <returns></returns>
        private string GetRelationId(IObject obj1, IObject obj2)
        {
            if (obj1 != obj2 && BindingMap.Contains(string.Format("{0}-{1}", obj2, obj1)))
            {
                return obj2.ID.ToString() + "Rel" + obj1.ID.ToString();
            }
            return obj1.ID.ToString() + "Rel" + obj2.ID.ToString();
        }

        public override void Relation(Connection.Action action, IObject obj1, IObject obj2)
        {
            switch (action)
            {
                case Action.Add:
                    {
                        if (obj2 is IExtraRelationData)
                        {
                            AddRelationParameters(obj1, obj2);
                        }
                        else
                        {
                            string objKind1 = ObjectKind(obj1.ObjType);
                            string objKind2 = ObjectKind(obj2.ObjType);
                            string relationType = objKind1 + objKind2;

                            string part1Uri = String.Concat("/Trident/" + objKind1 + "/", obj1.ID + ".xml");
                            string part2Uri = String.Concat("/Trident/" + objKind2 + "/", obj2.ID + ".xml");
                            string relation1Id = "Rel" + obj1.ID.ToString() +  obj2.ID.ToString();
                            string relation2Id = "Rel" + obj2.ID.ToString() + obj1.ID.ToString();
                            try
                            {
                                PackagePart part1 = myPackage.GetPart(new Uri(part1Uri, UriKind.Relative));
                                PackagePart part2 = myPackage.GetPart(new Uri(part2Uri, UriKind.Relative));
                                if (null != part1 && null != part2)
                                {
                                    part1.CreateRelationship(new Uri(part2Uri, UriKind.Relative), TargetMode.Internal,
                                        relationType, relation1Id);
                                    part2.CreateRelationship(new Uri(part1Uri, UriKind.Relative), TargetMode.Internal,
                                        relationType, relation2Id);
                                }
                            }
                            catch (Exception e)
                            {
                                throw new DataAccessException("Error while getting package element", e);
                            }
                        }
                    }
                    break;
                case Action.Update:
                    {
                        if (obj2 is IExtraRelationData)
                        {
                        }
                    }
                    break;
                case Action.Delete:
                    {
                        if (obj2 is IExtraRelationData)
                        {
                        }
                        else
                        {
                            // Since for every relationship we create two relationships when 
                            // deleting both should be removed.
                            string objKind1 = ObjectKind(obj1.ObjType);
                            string objKind2 = ObjectKind(obj2.ObjType);

                            string part1Uri = String.Concat("/Trident/" + objKind1 + "/", obj1.ID + ".xml");
                            string part2Uri = String.Concat("/Trident/" + objKind2 + "/", obj2.ID + ".xml");

                            try
                            {
                                PackagePart part1 = myPackage.GetPart(new Uri(part1Uri, UriKind.Relative));
                                PackagePart part2 = myPackage.GetPart(new Uri(part2Uri, UriKind.Relative));

                                string relation1Id = "Rel" + obj1.ID.ToString() + obj2.ID.ToString();
                                string relation2Id = "Rel" + obj2.ID.ToString() + obj1.ID.ToString();

                                part1.DeleteRelationship(relation1Id);
                                part2.DeleteRelationship(relation2Id);
                                myPackage.Flush();
                            }
                            catch (Exception e)
                            {
                                throw new DataAccessException("Error while getting package element", e);
                            }
                        }
                    }
                    break;
            }
            myPackage.Flush();
        }

                /// <summary>
        /// Performs operations on a relationship between two objects
        /// </summary>
        /// <param name="action"></param>
        /// <param name="obj1"></param>
        /// <param name="field"></param>
        /// <param name="obj2Type"></param>
        /// <param name="creator"></param>
        /// <param name="results"></param>
        public override void Relation(Connection.Action action, IObject obj1, string field, Connection.Storage obj2Type, Connection.ObjectCreator creator, out List<IObject> results)
        {
            results = new List<IObject>();
            switch (action)
            {
                case Action.GetByObject:
                    try
                    {
                        if (obj2Type != null && obj2Type.Flags == StorageFlags.RelationObject)
                        {
                            // We have a relationship object with custom data
                        }
                        else if (field != null)
                        {
                            // Normal 1-N join
                            string objKind = ObjectKind(obj2Type);
                            if (this.blobHandlers.ContainsKey(objKind))
                            {
                                BlobHandlerBase.FetchContents fetch = this.blobHandlers[objKind].fetchContents;
                                fetch(obj1, field, obj2Type, creator, myPackage, this, out results);
                                return;
                            }
                            else
                                GetRecordsByField(obj1, field, obj2Type, creator, out results);
                        }
                        else
                        {
                            // Normal N-N join
                            ManualJoin(action, obj1, obj2Type, creator, out results);
                        }
                    }
                    catch (RecordNotFoundException)
                    {
                        results = new List<IObject>();
                    }
                    break;
                default:
                    throw new NotImplementedException("Not implemented yet");
            }
        }

        protected override void ManualSearchRecords(Connection.Storage obj, string text, Connection.ObjectCreator creator, out List<IObject> results)
        {
            // Note: Not Implemented now coz searching is not an immediate requirement.
            throw new NotImplementedException();
        }

        protected override void UpdateRecord(Connection.Storage obj, List<Parameter> paramList)
        {
            Parameter idParam = Parameter.GetParamByName("ID", paramList);
            string objKind = ObjectKind(obj);

            if (this.blobHandlers.ContainsKey(objKind))
            {
                BlobHandlerBase.SaveContents save = this.blobHandlers[objKind].saveContents;
                save(myPackage, obj, null, idParam, objKind, this, ref paramList);
                return;
            }

            string elemUri = String.Concat("/Trident/" + objKind + "/", idParam.Value.ToString() + ".xml");
            XmlNode partDetails = GetPartFromId(elemUri)[0];

            if (null == partDetails)
            {
                throw new RecordNotFoundException("Record To update does not exist");
            }

            Parameter dateUpdated = Parameter.GetParamByName("UpdatedDate", paramList);
            dateUpdated.Value = DateTime.Now;
            XmlDocument doc = new XmlDocument();
            XmlNode updatedNode = doc.CreateNode(XmlNodeType.Element, "ObjectDetails", "");

            List<Parameter> newParameters = new List<Parameter>();
            foreach (Parameter param in paramList)
            {
                bool found = false;
                foreach (XmlNode paramDetailNode in partDetails.ChildNodes)
                {
                    if (paramDetailNode.Name == param.Name)
                    {
                        paramDetailNode.InnerText = param.Value == null ? "" : param.Value.ToString();
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    newParameters.Add(param);
                }
            }

            updatedNode.InnerXml = partDetails.InnerXml;
            foreach (Parameter param in newParameters)
            {
                XmlNode paramNode = doc.CreateNode(XmlNodeType.Element, param.Name, "");
                paramNode.InnerText = (param.Value == null) ? "" : param.Value.ToString();

                XmlAttribute paramType = doc.CreateAttribute("Type");
                paramType.Value = param.Type.ToString();
                paramNode.Attributes.Append(paramType);

                updatedNode.AppendChild(paramNode);
            }
            doc.AppendChild(updatedNode);
            
            UpdatePartInPackage(objKind, idParam.Value.ToString(), doc);
            myPackage.Flush();

            if (IsPropHandlerPresent(objKind, PropAction.Update))
            {
                PropHandlerBase.UpdateHandler update = this.propHandlers[objKind].updateHandler;
                update(obj, paramList, objKind, ref myPackage, "trident/tempobject");
            }
        }

        protected override void DeleteRecord(Connection.Storage obj, List<Parameter> paramList)
        {
            string objKind = ObjectKind(obj);
            Parameter idParam = Parameter.GetParamByName("ID", paramList);

            DeleteRecord(objKind, (Guid)idParam.Value);
        }


        protected override void DeleteRecord(string objKind, Guid id)
        {
            // A package part could have relationships with other parts. For every 
            // relationship we created a two way connection. The reason is that
            // when a part is queried for relationships it returns only those
            // where the part is a target. So when we are deleting this part we should
            // go to those parts to which the part in question is related and delete
            // relationships explicitly.
            string elemUri = String.Concat("/Trident/" + objKind + "/", id.ToString() + ".xml");

            PackagePart deletePart = myPackage.GetPart(new Uri(elemUri, UriKind.Relative));
            if (deletePart == null)
            {
                throw new RecordNotFoundException("Element to be deleted does not exist");
            }
            
            PackageRelationshipCollection relationShips = deletePart.GetRelationships();
            foreach (PackageRelationship rel in relationShips)
            {
                PackagePart targetPart = myPackage.GetPart(rel.TargetUri);
                PackageRelationshipCollection targetRels = targetPart.GetRelationships();
                string relId = "";
                foreach (PackageRelationship targetRel in targetRels)
                {
                    if (targetRel.TargetUri == deletePart.Uri)
                    {
                        relId = targetRel.Id;
                        break;
                    }
                }
                if (!string.IsNullOrEmpty(relId))
                {
                    targetPart.DeleteRelationship(relId);
                    myPackage.Flush();
                }
            }

            myPackage.DeletePart(new Uri(elemUri, UriKind.Relative));

            myPackage.Flush();

            if (IsPropHandlerPresent(objKind, PropAction.Delete))
            {
                PropHandlerBase.DeleteHandler delete = this.propHandlers[objKind].deleteHandler;
                delete(objKind, id, ref myPackage);
            }
        }

        protected override void ExecuteRetrieve(Connection.Storage obj1, string XMLQuery,
            Connection.ObjectCreator creator, out List<IObject> results)
        {
            // The OPC does not support executing queries so this style of retrieveing 
            // cannot be supported.
            throw new NotSupportedException();
        }

        protected override DbDataReader ExecuteRetrieveReader(string xPathQuery)
        {
            // The OPC does not support executing queries so this style of retrieveing 
            // cannot be supported.
            throw new NotSupportedException();
        }

        /// <summary>
        /// (Debug) *DANGER* Erases all database content.
        /// </summary>
        public void ZapContainer()
        {
            PackagePartCollection parts = myPackage.GetParts();
            
            Uri[] partsUri = new Uri[parts.ToArray().Length];
            int counter = 0;
            foreach (PackagePart part in parts)
            {
                partsUri[counter++] = part.Uri;
            }

            foreach (Uri uri in partsUri)
            {
                myPackage.DeletePart(uri);
            }
        }


        private void GetRecordsByField(IObject obj1, string field, Connection.Storage obj2Type, Connection.ObjectCreator creator, out List<IObject> results)
        {
            results = new List<IObject>();
            List<XmlNode> elements = new List<XmlNode>();

            string partToSearch = "/Trident/" + ObjectKind(obj2Type);
            string xQuery = string.Format("ObjectDetails[{0}ID = '{1}']", field, obj1.ID);
            PackagePartCollection parts = myPackage.GetParts();
            foreach(PackagePart part in parts)
            {
                if (part.Uri.OriginalString.StartsWith(partToSearch)
                    && part.ContentType != "application/vnd.openxmlformats-package.relationships+xml")
                {
                    XmlDocument partXml = GetPartAsXml(part);
                    if (null != partXml.SelectSingleNode(xQuery))
                        elements.Add(partXml.FirstChild);
                }
            }

            OPCDataReader reader = new OPCDataReader(elements);
            while (reader.Read())
            {
                results.Add(creator(reader, this));
            }
        }

        /// <summary>
        /// Manually do the join
        /// </summary>
        /// <param name="action"></param>
        /// <param name="obj1"></param>
        /// <param name="obj2Type"></param>
        /// <param name="creator"></param>
        /// <param name="results"></param>
        protected override void ManualJoin(Connection.Action action, IObject obj1, Connection.Storage obj2Type, 
            Connection.ObjectCreator creator, out List<IObject> results)
        {
            results = new List<IObject>();
            List<XmlNode> elements = new List<XmlNode>();

            string elemUri = String.Concat("/Trident/" + ObjectKind(obj1.ObjType) + "/", obj1.ID.ToString() + ".xml");

            try
            {
                PackagePart part = myPackage.GetPart(new Uri(elemUri, UriKind.Relative));

                if (null == part)
                    throw new RecordNotFoundException("Source Element for the relationship does not exist");

                string targetElemUri = "/Trident/" + ObjectKind(obj2Type);
                PackageRelationshipCollection relations = part.GetRelationships();
                foreach (PackageRelationship rel in relations)
                {
                    if (true == rel.TargetUri.OriginalString.StartsWith(targetElemUri))
                    {
                        PackagePart targetPart = myPackage.GetPart(rel.TargetUri);
                        XmlDocument doc = GetPartAsXml(targetPart);
                        elements.Add(doc.FirstChild);
                    }
                }

                OPCDataReader reader = new OPCDataReader(elements);
                while (reader.Read())
                {
                    results.Add(creator(reader, this));
                }
            }
            catch (Exception e)
            {
                throw new DataAccessException("Error while getting package element", e);
            }
        }

        #region Private Methods

        private void AddRelationParameters(IObject obj1, IObject obj2)
        {
            Guid dummyRelId = Guid.NewGuid();

            Connection.Storage obj1Type = obj1.ObjType;
            Connection.Storage obj2Type = obj2.ObjType;

            string objKind1 = ObjectKind(obj1.ObjType);
            string objKind2 = ObjectKind(((IExtraRelationData)obj2).ChildObj.ObjType);
            string relObjKind = ObjectKind(obj2.ObjType);

            string rel1Type = objKind1 + relObjKind;
            string rel2Type = objKind2 + relObjKind;
            string relation1Id = "Rel" + obj1.ID.ToString() + dummyRelId.ToString();
            string relation2Id = "Rel" + ((IExtraRelationData)obj2).ChildID + dummyRelId.ToString();


            // After the record is inserted create OPC level relationships between
            // this newly created part and the two owner parts.
            Uri elemUriPart1 = new Uri(String.Concat("/Trident/" + objKind1 + "/", obj1.ID + ".xml"), UriKind.Relative);
            Uri elemUriPart2 = new Uri(String.Concat("/Trident/" +
                objKind2 + "/", ((IExtraRelationData)obj2).ChildID + ".xml"), UriKind.Relative);
            

            List<Parameter> paramList = new List<Parameter>();
            paramList.Add(new Parameter("ID", Parameter.ParamType.Guid, Parameter.ParamDirection.In, dummyRelId));

            if (obj2.ObjType.IsSelfRelation)
                paramList.Add(new Parameter(string.Format("{0}_1ID", obj1.ObjType), Parameter.ParamType.Guid, Parameter.ParamDirection.In, obj1.ID));
            else
                paramList.Add(new Parameter(string.Format("{0}ID", obj1.ObjType), Parameter.ParamType.Guid, Parameter.ParamDirection.In, obj1.ID));

            IExtraRelationData extra = (IExtraRelationData)obj2;
            paramList.AddRange(extra.ExtraData);

            if (obj2.ObjType.IsSelfRelation)
                paramList.Add(new Parameter(string.Format("{0}_2ID", obj1.ObjType), Parameter.ParamType.Guid, Parameter.ParamDirection.In, extra.ChildID));
            else
                paramList.Add(new Parameter(string.Format("{0}ID", extra.ChildType), Parameter.ParamType.Guid, Parameter.ParamDirection.In, extra.ChildID));

            obj1Type = obj2.ObjType;
            obj2Type = null;

            Parameter createdBy = new Parameter("CreatedBy", Parameter.ParamType.String, Parameter.ParamDirection.In, Environment.UserName);
            Parameter createdDate = new Parameter("CreatedDate", Parameter.ParamType.DateTime, Parameter.ParamDirection.Out);
            paramList.Add(createdBy);
            paramList.Add(createdDate);

            Parameter updatedBy = new Parameter("UpdatedBy", Parameter.ParamType.String, Parameter.ParamDirection.In, Environment.UserName);
            Parameter updatedDate = new Parameter("UpdatedDate", Parameter.ParamType.DateTime, Parameter.ParamDirection.Out);
            paramList.Add(updatedBy);
            paramList.Add(updatedDate);

            InsertRecord(obj1Type, obj2Type, dummyRelId, ref paramList);

            Uri newPartUri = new Uri(String.Concat("/Trident/" + ObjectKind(obj1Type, obj2Type) + "/", dummyRelId + ".xml"), UriKind.Relative);

            try
            {
                PackagePart part1 = myPackage.GetPart(elemUriPart1);
                PackagePart part2 = myPackage.GetPart(elemUriPart2);

                part1.CreateRelationship(newPartUri, TargetMode.Internal, rel1Type, relation1Id);
                part2.CreateRelationship(newPartUri, TargetMode.Internal, rel2Type, relation2Id);
            }
            catch (Exception e)
            {
                throw new DataAccessException("Error while getting package element", e);
            }

        }

        /// <summary>
        /// This method looks a map to find if the uri for a type
        /// exists. If it does then it returns that else this method
        /// returns a generic URI string.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private string GetUri(string type)
        {
            if (type == "AssemblyImageBlob" || type == "XomlContents")
                return "trident/tempobject";
            return "";
        }

        private void UpdatePartInPackage(string objKind, string objId, XmlDocument updatedDoc)
        {
            try
            {
                string elemUri = String.Concat("/Trident/" + objKind + "/", objId.ToString() + ".xml");

                PackagePart part = myPackage.GetPart(new Uri(elemUri, UriKind.Relative));
                Stream partStream = part.GetStream();

                //XmlDocument doc = new XmlDocument();
                //doc.AppendChild(partDetails);

                System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
                byte[] data = encoding.GetBytes(updatedDoc.OuterXml);

                partStream.Write(data, 0, data.Length);
            }
            catch (Exception e)
            {
                throw new DataAccessException("Error while getting package element", e);
            }

        }

        public List<XmlNode> GetPartFromId(string uri)
        {
            try
            {
                List<XmlNode> nodes = new List<XmlNode>();
                Uri query = new Uri(uri, UriKind.Relative);
                PackagePart xmlPart = myPackage.GetPart(query);

                XmlDocument partDoc = GetPartAsXml(xmlPart);
                nodes.Add(partDoc.FirstChild);
                return nodes;
            }
            catch (Exception e)
            {
                throw new DataAccessException("Error while getting package element", e);
            }
        }

        private List<XmlNode> GetPartFromName(string name)
        {
            List<XmlNode> nodes = new List<XmlNode>();
            PackagePartCollection parts = myPackage.GetParts();
            foreach (PackagePart part in parts)
            {
                XmlDocument partDoc = GetPartAsXml(part);
                if (null == partDoc)
                    continue;
                string xQuery = "ObjectDetails/Name";
                XmlNode partNode = partDoc.SelectSingleNode(xQuery);
                if (null != partNode && (partNode.InnerText == name))
                {
                    nodes.Add(partDoc.FirstChild);
                    break;
                }
            }
            return nodes;
        }

        private List<XmlNode> GetAllPartsOfType(string type)
        {
            string partUri = "/Trident/" + type;
            List<XmlNode> nodes = new List<XmlNode>();
            PackagePartCollection parts = myPackage.GetParts();
            foreach (PackagePart part in parts)
            {
                if ((true == part.Uri.OriginalString.StartsWith(partUri)) &&
                    (false == part.Uri.OriginalString.StartsWith(partUri + "/_rels"))
                    && (System.Net.Mime.MediaTypeNames.Application.Octet != part.ContentType)
                    && ("trident/tempobject" != part.ContentType))
                {
                    XmlDocument partDoc = GetPartAsXml(part);
                    nodes.Add(partDoc.FirstChild);
                }
            }
            return nodes;
        }

        public XmlDocument GetPartAsXml(PackagePart part)
        {
            Stream partData = part.GetStream();
            byte[] data = new byte[partData.Length];

            partData.Read(data, 0, data.Length);
            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
            string xmlContents = enc.GetString(data);

            XmlDocument newDoc = new XmlDocument();
            newDoc.LoadXml(xmlContents);
            return (newDoc);
        }

        #endregion

        #region Property Handlers

        private Dictionary<string, PropHandlerBase> propHandlers = new Dictionary<string, PropHandlerBase>();

        private Dictionary<string, BlobHandlerBase> blobHandlers = new Dictionary<string, BlobHandlerBase>();

        private void CreateBlobHandlers()
        {
            TypeHandlersHelper.GetBlobHandlers("", ref blobHandlers);
        }

        private void CreatePropHandlers()
        {
            TypeHandlersHelper.GetProviders("", ref propHandlers);
        }

        private bool IsPropHandlerPresent(string objectName, PropAction action)
        {
            bool isPresent = false;
            if (this.propHandlers.ContainsKey(objectName))
            {
                PropHandlerBase handlers = this.propHandlers[objectName];
                
                switch(action)
                {
                    case PropAction.Fetch:
                        isPresent = !(handlers.fetchHandler == null);
                        break;
                    case PropAction.Update:
                        isPresent = !(handlers.updateHandler == null);
                        break;
                    case PropAction.Delete:
                        isPresent = !(handlers.deleteHandler == null);
                        break;
                    case PropAction.Insert:
                        isPresent = !(handlers.insertHandler == null);
                        break;
                }
            }
            return isPresent;
        }

        #endregion

        #region Custom Public Methods

        /// <summary>
        /// This method returns the package object which this class abstracts.
        /// It is not advisable to use this method to get a handle to the package
        /// object itself, especially when working with inside Trident objects.
        /// This method has been introduced for giving a refernece to the package
        /// for scenarios where integration with external partners like MyExperiment
        /// is needed.
        /// </summary>
        /// <returns>
        /// Package object.
        /// </returns>
        public static Package GetPackageReference()
        {
            return myPackage;
        }

        #endregion
        /// <summary>
        /// overridden serialization method
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
        {
            throw new NotImplementedException();
        }
    }

    internal enum PropAction
    {
        Insert,

        Fetch,

        Update,

        Delete,
    }

}
